#include "T32H264LiveSource.hh"

#include <sys/time.h>
#include <string.h>

#include "../service/Logger.hh"

T32H264LiveSource* T32H264LiveSource::createNew(UsageEnvironment& env) {
    return new T32H264LiveSource(env);
}

T32H264LiveSource::T32H264LiveSource(UsageEnvironment& env)
    : FramedSource(env),
      fEventTriggerId(0),
      fTaskToken(0) {
    fEventTriggerId = envir().taskScheduler().createEventTrigger(deliverFrame0);
    LOG_INFO("T32H264LiveSource created (event trigger id=%u)", fEventTriggerId);
}

T32H264LiveSource::~T32H264LiveSource() {
    cancelNextTick();
    if (fEventTriggerId != 0) {
        envir().taskScheduler().deleteEventTrigger(fEventTriggerId);
        fEventTriggerId = 0;
    }
    LOG_INFO("T32H264LiveSource destroyed");
}

void T32H264LiveSource::signalNewFrame() {
    if (fEventTriggerId != 0) {
        LOG_DEBUG("New frame notification received, triggering event");
        envir().taskScheduler().triggerEvent(fEventTriggerId, this);
    }
}

void T32H264LiveSource::doGetNextFrame() {
    cancelNextTick();
    deliverFrame();
}

void T32H264LiveSource::doStopGettingFrames() {
    cancelNextTick();
    FramedSource::doStopGettingFrames();
}

unsigned T32H264LiveSource::maxFrameSize() const {
    return kMaxFrameBytes;
}

void T32H264LiveSource::deliverFrame0(void* clientData) {
    auto* self = static_cast<T32H264LiveSource*>(clientData);
    if (self != nullptr) {
        self->cancelNextTick();
        self->deliverFrame();
    }
}

void T32H264LiveSource::deliverFrame() {
    static unsigned emptyPolls = 0;

    if (!isCurrentlyAwaitingData()) {
        return;
    }

    ringbuf info;
    memset(&info, 0, sizeof(info));

    int bytesRead = ringget(&info);
    if (bytesRead <= 0 || info.buffer == nullptr) {
        emptyPolls++;
        if (emptyPolls % 1000 == 0) {
            LOG_DEBUG("Ring buffer empty (polls=%u), scheduling retry", emptyPolls);
        }
        scheduleNextTick(1000);
        return;
    }

    unsigned frameSize = static_cast<unsigned>(bytesRead);
    if (frameSize == 0) {
        scheduleNextTick(1000);
        return;
    }

    if (frameSize > fMaxSize) {
        fFrameSize = fMaxSize;
        fNumTruncatedBytes = frameSize - fMaxSize;
        LOG_WARNING("Video frame truncated: original=%u bytes, truncated=%u bytes", frameSize, fNumTruncatedBytes);
    } else {
        fFrameSize = frameSize;
        fNumTruncatedBytes = 0;
    }

    emptyPolls = 0;

    if (fFrameSize > 0) {
        memcpy(fTo, info.buffer, fFrameSize);
    }

    struct timeval now{};
    gettimeofday(&now, nullptr);
    fPresentationTime = now;

    LOG_DEBUG("Delivering frame: size=%u truncated=%u", fFrameSize, fNumTruncatedBytes);
    FramedSource::afterGetting(this);
}

void T32H264LiveSource::scheduleNextTick(unsigned microseconds) {
    if (fTaskToken != 0) {
        return;
    }
    fTaskToken = envir().taskScheduler().scheduleDelayedTask(
        microseconds, (TaskFunc*)deliverFrame0, this);
}

void T32H264LiveSource::cancelNextTick() {
    if (fTaskToken != 0) {
        envir().taskScheduler().unscheduleDelayedTask(fTaskToken);
        fTaskToken = 0;
    }
}
